/*->c2.bf */


#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "os.h"
#include "bbc.h"
#include "h.sprite"
#include "h.wimp"
#include "h.werr"
#include "h.flex"

#include "h.etc"

#include "h.wos"
#include "h.fsx"

#include "h.bf"



/* BUFFCRIT is a flag set if p has been moved outside the buffer */


/* dump the current buffer full */

static os_error * bf_dump(buffer * bf)
{
 os_error * err;

 err=NULL;

 if(bf->flags & BUFFMOD)
 {
  if(bf->length)
  {
            err=fs_seek(bf->fh,bf->base);
   if(!err) err=fs_write(bf->fh,bf->data,bf->length);
  }
  bf->flags&=~BUFFMOD;
 }

 return(err);
}



/* load the current buffer from current pointer */

static os_error * bf_load(buffer * bf)
{
 os_error * err;

 err=fs_seek(bf->fh,bf->p);
 if(!err)
 {
  bf->base=bf->p;
  err=fs_readn(bf->fh,bf->data,bf->size,&bf->length);
 }

 bf->flags&=~BUFFCRIT;

 return(err);
}


static os_error * bf_init(buffer * bf)
{
 bf->fh=0;
 bf->p=0;
 bf->flags=0;
 bf->length=0;
 bf->base=0;
 return(NULL);
}


static os_error * bf_alloc(buffer * bf,int size)
{
 os_error * err;

 err=bf_init(bf);

 do
 {
  if(flex_alloc((flex_ptr)&bf->data,size))
  {
   bf->size=size;
   break;
  }
  size/=2;
 } while(size>128);

 return(err);
}


static os_error * bf_dealloc(buffer * bf,os_error * ep)
{
 flex_free((flex_ptr)&bf->data);
 return(ep);
}


os_error * bf_tell(buffer * bf,int * posn)
{
 *posn=bf->p;
 return(NULL);
}


static os_error * bf_extent(buffer * bf,int * ex)
{
 os_error * err;
 int        fx;
 int        lx;

 err=fs_extent(bf->fh,&fx);

 lx=MAX(bf->p,bf->base+bf->length);

 *ex=MAX(lx,fx);

 return(err);
}



/* SEEK_SET 0 start of stream (see fseek) */
/* SEEK_CUR 1 current position in stream (see fseek) */
/* SEEK_END 2 end of stream (see fseek) */

os_error * bf_seek(buffer * bf,int posn,int whence)
{
 int ex;

 if(whence==SEEK_CUR) posn+=bf->p;
 else
 if(whence==SEEK_END)
 {
  bf_extent(bf,&ex);
  posn+=ex;
 }

 if(posn<bf->base || posn>(bf->base+bf->size)) bf->flags|=BUFFCRIT;
 if(posn>(bf->base+bf->length))
 {
  bf->length=bf->p-bf->base;
  if(bf->length>bf->size) bf->length=bf->size;
 }

 bf->p=posn;
 return(NULL);
}




os_error * bf_eof(buffer * bf,int * eof)
{
 os_error * err;

 err=NULL;

 if(bf->flags & BUFFCRIT)
 {
  err=bf_dump(bf);
  if(!err) err=bf_load(bf);
 }

 if(bf->p<(bf->base+bf->length)) *eof=0;
 else                            *eof=1;

 return(err);
}



os_error * bf_readn(buffer * bf,void * b,int len,int * read)
{
 os_error * err;
 char     * p;
 int        left;
 int        chunk;
 int        n;
 int        eof;

 err=NULL;
 left=len;
 eof=0;
 p=(char*)b;

 if(left)
 {
  if(!(bf->flags & BUFFCRIT))
  {
   /* see how much we can supply from the buffer */

   chunk=bf->length-(bf->p-bf->base);
   if(chunk>0)
   {
    if(chunk>left) chunk=left;

    memcpy(p,bf->data+(bf->p-bf->base),chunk);
    p+=chunk;

    bf->p+=chunk;
    if(bf->p>=(bf->base+bf->length)) bf->flags|=BUFFCRIT;

    left-=chunk;
   }
  }

  if(left)
  {
   if(left>bf->size) /* try to read integer number of buffers direct full */
   {
    chunk=(left/bf->size);
    chunk*=bf->size;

    err=fs_seek(bf->fh,bf->p);
    if(!err)
    {
     err=fs_readn(bf->fh,p,chunk,&n);
    }

    if(n<chunk) eof=1;

    p+=n;

    bf->p+=n;
    if(bf->p>=(bf->base+bf->length)) bf->flags|=BUFFCRIT;

    left-=n;
   } 

   if(left && !eof && !err)
   {
    /* then try to fill the buffer */

    if(bf->flags & BUFFCRIT) err=bf_dump(bf);
    if(!err) err=bf_load(bf);

    /* then complete the read with what is in the buffer */

    if(!err)
    {
     chunk=bf->length-(bf->p-bf->base);
     if(chunk>0)
     {
      if(chunk>left) chunk=left;

      memcpy(p,bf->data+(bf->p-bf->base),chunk);
      p+=chunk;

      bf->p+=chunk;
      if(bf->p>=(bf->base+bf->length)) bf->flags|=BUFFCRIT;

      left-=chunk;
     }
    }
   }
  }
 }
 *read=len-left;
 return(err);
}



os_error * bf_writen(buffer * bf,void * b,int len,int * write)
{
 os_error * err;
 char     * p;
 int        left;
 int        chunk;
 int        n;

 err=NULL;
 left=len;
 p=(char*)b;

 if(left)
 {
  if(!(bf->flags & BUFFCRIT))
  {
   /* see how much we can write to the buffer */

   chunk=bf->size-(bf->p-bf->base);
   if(chunk>0)
   {
    if(chunk>left) chunk=left;

    memcpy(bf->data+(bf->p-bf->base),p,chunk);
    bf->flags|=BUFFMOD;

    p+=chunk;

    bf->p+=chunk;
    if(bf->p>=(bf->base+bf->size))  bf->flags|=BUFFCRIT;
    if(bf->p>(bf->base+bf->length)) bf->length=bf->p-bf->base;

    left-=chunk;
   }
  }

  if(bf->flags & BUFFCRIT)
  {
   err=bf_dump(bf);
   bf->length=0;
   bf->base=bf->p;
   bf->flags&=~BUFFCRIT;
  }

  if(!err && left)
  {
   if(left>bf->size) /* try to write integer number of buffers direct full */
   {
    chunk=(left/bf->size);
    chunk*=bf->size;

    err=fs_seek(bf->fh,bf->p);
    if(!err)
    {
     err=fs_writen(bf->fh,p,chunk,&n);
    }

    p+=n;

    bf->p+=n;
    bf->length=0;
    bf->base=bf->p;
    bf->flags&=~BUFFCRIT;

    left-=n;
   } 

   if(!err && left)
   {
    /* then complete the write to the buffer */

    if(!err)
    {
     chunk=bf->size-(bf->p-bf->base);
     if(chunk>0)
     {
      if(chunk>left) chunk=left;

      memcpy(bf->data+(bf->p-bf->base),p,chunk);
      bf->flags|=BUFFMOD;

      p+=chunk;

      bf->p+=chunk;
      if(bf->p>=(bf->base+bf->size))  bf->flags|=BUFFCRIT;
      if(bf->p>(bf->base+bf->length)) bf->length=bf->p-bf->base;

      left-=chunk;
     }
    }
   }
  }
 }

 *write=len-left;

 return(err);
}




os_error * bf_open(char * filename,int mode,int size,buffer * bf)
{
 os_error * err;

 err=bf_alloc(bf,size);
 if(!err)
 {
  bf->flags|=BUFFCRIT;

  err=fs_open(filename,mode,&bf->fh);
  if(err) err=bf_dealloc(bf,err);
 }

 return(err);
}



os_error * bf_close(buffer * bf,os_error * ep)
{
 os_error * err;

 err=NULL;

 if(bf->data)
 {
  err=bf_dump(bf);

  err=fs_close(bf->fh,err);

  err=bf_dealloc(bf,err);
 }

 if(ep) return(ep);
 else   return(err);
}


